package standalone.sna.community;

import java.io.DataOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import reducible.VectorReducible;
import utils.DataInput;
import utils.MathUtils;

import cern.colt.list.DoubleArrayList;
import cern.colt.list.IntArrayList;
import cern.colt.matrix.DoubleFactory1D;
import cern.colt.matrix.DoubleMatrix1D;
import cern.colt.matrix.DoubleMatrix2D;

/**
 * Composite Community Detection by *Hard* Label Propagation
 * @author purlin
 *
 */
public class CompositeLP extends LabelPropagation {
	/**
	 * Number of networks
	 */
	protected int numNetworks;
	/**
	 * Constructor
	 * @param numUsers: number of users
	 * @param numCommunities: number of communities
	 */
	public CompositeLP(int numUsers, int numCommunities) {
		super(numUsers, numCommunities);
		this.numNetworks = 1;
	}
	/**
	 * Constructor
	 * @param numUsers: number of users
	 * @param numCommunities: number of communities
	 * @param numNetworks: number of subnetworks
	 */
	public CompositeLP(int numUsers, int numCommunities, int numNetworks) {
		super(numUsers, numCommunities);
		this.numNetworks = numNetworks;
	}
	/**
	 * Perform Label Propagation
	 * @param neighborList: link table
	 * @param idList: user ids
	 * @param numIt: number of iterations
	 * @throws IOException
	 */
	public void performLP(DoubleMatrix2D[] neighborList, List<Integer> idList, int numIt) throws IOException {
		if(numNetworks==1) this.performLP(neighborList[0], idList, numIt);
		else {
			//Community index Initialization 
			for(Integer id: idList) w.vectors.set(id, Math.floor((Math.random()*numCommunities)));
			w = (VectorReducible) reduceObj.run(w);
			//Data parameters
			IntArrayList cidList = new IntArrayList();
			DoubleArrayList cvalueList = new DoubleArrayList();
			//Iteration
			for(int i=0;i<numIt;i++){
				DoubleMatrix1D currentIDs = DoubleFactory1D.sparse.make(numUsers);
				for(int n=0;n<numNetworks;n++) {				
					//Get neighbors' labels
					for(int j=0;j<neighborList[n].rows();j++){
						int[] comCount = new int[numCommunities];
						neighborList[n].viewRow(j).getNonZeros(cidList, cvalueList);
						for(int k=0;k<cidList.size();k++) comCount[(int)cvalueList.get(k)] += 1;
						currentIDs.set(idList.get(j), MathUtils.getMaxID(comCount));
					}
				}
				VectorReducible cw = new VectorReducible(currentIDs);
				w = (VectorReducible) reduceObj.run(cw);
			}
		}
	}
	/**
	 * Build Model
	 * @param inputPath: input paths
	 * @param outputPath: output path for network indicators
	 * @param dataType: data format: pair list or link table
	 * @param modelName: name of model to save
	 * @param numUsers: number of users:
	 * @param numCommunities: number of communities
	 * @param numIt: number of iterations
	 * @param master: master IP
	 * @param masterPort: master port
	 * @throws NumberFormatException
	 * @throws IOException
	 */
	public static void buildModel(String[] inputPath, String outputPath, String dataType, String modelName, int numUsers, int numCommunities, int numIt, String master, int masterPort) throws NumberFormatException, IOException {
		//Read data
		List<Integer> idList = new ArrayList<Integer>();
		DoubleMatrix2D[] instances = new DoubleMatrix2D[inputPath.length];
		for(int i=0;i<inputPath.length;i++){
			if("pair".endsWith(dataType)) instances[i] = DataInput.readPairList(inputPath[i], numUsers, idList);
			else if("link".endsWith(dataType)) instances[i] = DataInput.readDenseMatrix(inputPath[i], numUsers, idList);
		}
		//Model building
		CompositeLP lpObj = new CompositeLP(numUsers, numCommunities, inputPath.length);
		lpObj.initialize(master, masterPort);
		lpObj.performLP(instances, idList, numIt);
		//Model saving
		if(lpObj.getRank()==0){
			DataOutputStream out = new DataOutputStream(new FileOutputStream(new File(outputPath+"/"+modelName)));
			lpObj.saveModel(out);
			out.close();
		}
	}
}
